package utils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StreamCorruptedException;
import java.io.UnsupportedEncodingException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import connection.exception.ConnectionResetException;

/**
 * The Class Serialization is responsible for the conversion of the input to a canonical format which could
 * be sent by the underlying layer. Because in a earlier state of the development of the application we had
 * used object serialization we decided not to remove that from the source.
 */
public class Serialization {

	/** This flag set at compile time signals if that class converts the input to a xml format or to use
	 * object serialization; 	
	 */
	public static final boolean xmlSerialization = true;


	/** 
	 * This method analyzes the flag xmlSerialization to needle between object serialization or xml serialization.
	 * 
	 * @param data the object to be converted.
	 * 
	 * @return a byte array containing the data to be sent by the underlying layer.
	 */
	public static byte[] Serialize(Object data){
		if(xmlSerialization){
			String xml = SerializeAsXml(data);
			try {
				return xml.getBytes("UTF-8");
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
				return null;
			}
		}
		else
			return SerializeAsObject(data);
	}


	/** 
	 * This method analyzes the flag xmlSerialization to decided whether to make the backward serialization
	 * from object or from xml.
	 * 
	 * @param data the byte array containing the data to be converted.
	 * 
	 * @return the object converted that had been sent by the endpoint.
	 */
	public static Object Deserialize(byte[] data) throws ConnectionResetException{
		if(xmlSerialization)
			return DeserializeAsXml(data);
		else
			return DeserializeAsObject(data);
	}

	/**
	 * Given a object that method creates a new DOM document and initiates the serialization process of the
	 * object given in argument.
	 * 
	 * @param data the object to be converted.
	 * 
	 * @return the byte array containing the serialized object.
	 */
	public static String SerializeAsXml(Object data){
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db;
		try {
			db = dbf.newDocumentBuilder();			
			Document doc = db.newDocument();
			doc.setDocumentURI("http://www.w3.org/2001/XMLSchema");
			doc.setXmlStandalone(true);

			Element el = XmlSerializationUtils.getConversionObjectXml(data, doc);
			doc.appendChild(el);
			String xmlString = XmlSerializationUtils.xmlToString(doc);
			xmlString = xmlString.replaceFirst("UTF-8", "utf-8");
			System.out.println(xmlString);
			return xmlString;

		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}

		return null;
	}

	/**
	 * Given a byte array of data that had been previously converted makes the backward conversion of it.
	 * 
	 * @param data the byte array containing the data to be converted.
	 * 
	 * @return the object represented by the byte array given.
	 * @throws ConnectionResetException 
	 */
	private static Object DeserializeAsXml(byte[] data) throws ConnectionResetException{

		try {
			String xml = new String(data, "UTF-8");		
			String trimmedXml = xml.trim();

			if(xml.isEmpty())
				throw new ConnectionResetException();

			Document doc = XmlSerializationUtils.stringToXml(trimmedXml);
			Element el = doc.getDocumentElement();
			Object obj = XmlSerializationUtils.getConversionXmlObject(el);
			return obj;
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return null;
	}


	/**
	 * Given a abstract object that method serializes it using the object serialization of the Java Language.
	 * 
	 * @param data the object to be converted.
	 * 
	 * @return a byte array that contains the data to be sent.
	 */
	private static byte[] SerializeAsObject(Object data){

		ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
		ObjectOutputStream os;

		try {
			os = new ObjectOutputStream(new BufferedOutputStream(byteStream));
			os.flush();
			os.writeObject(data);
			os.flush();
			os.close();
			byte[] buf = byteStream.toByteArray();
			return buf;

		} catch (IOException e) {
			System.out.println("Excep��o lan�ada ao tentar serializar: "+e.getMessage());
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Given a byte array that have been converted using object serialization this method makes the backward
	 * conversion to object.
	 * 
	 * @param data the byte array containing the object to be converted.
	 * 
	 * @return the object before being converted.
	 * 
	 * @throws ConnectionResetException when the object received are corrupted.
	 */
	private static Object DeserializeAsObject(byte[] data) throws ConnectionResetException{

		try {
			ByteArrayInputStream byteStream = new ByteArrayInputStream(data);
			ObjectInputStream is = new ObjectInputStream(new BufferedInputStream(byteStream));
			Object deserialized = is.readObject();
			is.close();
			return deserialized;
		}catch (StreamCorruptedException e) {
			System.out.println("Excep��o lan�ada pois conex�o terminou");
			throw new ConnectionResetException();
		} catch (IOException e) {
			System.out.println("Excep��o lan�ada ao tentar des-serializar: "+e.getMessage());
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			System.out.println("Excep��o ao tentar fazer Cast � classe: "+e.getMessage());
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * This method serializes a given object to the file with the specified filename. If the file already
	 * exists in the system the file is deleted and created a new file with the new information of the
	 * object.
	 * 
	 * @param filename , the name of the file to serialize to.
	 * @param data , the object to be serialized.
	 */
	public static void SerializeToFile(String filename, Object data){
		try{

			File file = new File(filename);
			if(file.exists()){
				file.delete();
			}

			file.createNewFile();

			FileOutputStream fileOutput = new FileOutputStream(file);
			ObjectOutputStream objectOutput = new ObjectOutputStream(fileOutput);

			objectOutput.writeObject(data);

			objectOutput.flush();
			fileOutput.flush();			
			objectOutput.close();
			fileOutput.close();

		}catch(IOException ex){
			ex.printStackTrace();
		}
	}

	/**
	 * This method reads the data contained in the file with the specified filename. If there is no
	 * data in that file, or the file doesn't exists will be returned null.
	 * 
	 * @param filename , the file to deserialize from.
	 * @return the object resulting from deserialize the file with the given filename, or null
	 * if there is no such file.
	 */
	public static Object DeserializeFromFile(String filename){

		try{

			File file = new File(filename);

			if(file.exists()){

				FileInputStream fileInput = new FileInputStream(file);
				ObjectInputStream objectInput = new ObjectInputStream(fileInput);
				Object obj = objectInput.readObject();

				objectInput.close();
				fileInput.close();

				return obj;
			}

		}catch(FileNotFoundException e){
			e.printStackTrace();
		} catch (IOException e) {
			return null;
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}

}
